﻿#include "tcpsocket.h"
// linux
#ifdef Q_OS_LINUX
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <errno.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

TcpSocket::TcpSocket()
{
#ifdef Q_OS_WIN
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);
#endif
}

TcpSocket::TcpSocket(int connfd)
{
#ifdef Q_OS_WIN
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);
#endif
	m_socket = connfd;
}

TcpSocket::~TcpSocket()
{
#ifdef Q_OS_WIN
    WSACleanup();
#endif
}

int TcpSocket::connectToHost(QString ip, unsigned short port, int timeout)
{
	int ret = 0;
    if (port <= 0 || port > 65535)
	{
		ret = ParamError;
		return ret;
	}

	m_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (m_socket < 0)
	{
		ret = errno;
		printf("func socket() err:  %d\n", ret);
		return ret;
	}

	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(ip.toStdString().data());

    ret = connectTimeout(servaddr, timeout);

	return ret;
}

int TcpSocket::sendMsg(QString sendData, int timeout)
{
    string stdData = sendData.toStdString();
	// 返回0->没超时, 返回-1->超时
	int ret = writeTimeout(timeout);
	if (ret == 0)
	{
		int writed = 0;
        int dataLen = stdData.size() + 4;
		// 添加的4字节作为数据头, 存储数据块长度
		unsigned char *netdata = (unsigned char *)malloc(dataLen);
		if (netdata == NULL)
		{
			ret = MallocError;
			printf("func sckClient_send() mlloc Err:%d\n ", ret);
			return ret;
		}
		// 转换为网络字节序
        int netlen = htonl(stdData.size());
		memcpy(netdata, &netlen, 4);
        memcpy(netdata + 4, stdData.data(), stdData.size());

		// 没问题返回发送的实际字节数, 应该 == 第二个参数: dataLen
		// 失败返回: -1
		writed = writen(netdata, dataLen);
		if (writed < dataLen)	// 发送失败
		{
			if (netdata != NULL)
			{
				free(netdata);
				netdata = NULL;
			}
			return writed;
		}
		// 释放内存
		if (netdata != NULL) 
		{
			free(netdata);
			netdata = NULL;
		}
	}
	else
	{
		//失败返回-1，超时返回-1并且errno = ETIMEDOUT
		if (ret == -1 && errno == ETIMEDOUT)
		{
			ret = TimeoutError;
			printf("func sckClient_send() mlloc Err:%d\n ", ret);
		}
	}
	return ret;
}

QString TcpSocket::recvMsg(int timeout)
{
	// 返回0 -> 没超时就接收到了数据, -1, 超时或有异常
	int ret = readTimeout(timeout); 
	if (ret != 0)
	{
		if (ret == -1 || errno == ETIMEDOUT)
		{
			printf("readTimeout(timeout) err: TimeoutError \n");
            return QString();
		}
		else
		{
			printf("readTimeout(timeout) err: %d \n", ret);
            return QString();
		}
	}

	int netdatalen = 0;
	ret = readn(&netdatalen, 4); //读包头 4个字节
	if (ret == -1)
	{
		printf("func readn() err:%d \n", ret);
        return QString();
	}
	else if (ret < 4)
	{
		printf("func readn() err peer closed:%d \n", ret);
        return QString();
	}

	int n = ntohl(netdatalen);
	// 根据包头中记录的数据大小申请内存, 接收数据
	char* tmpBuf = (char *)malloc(n + 1);
	if (tmpBuf == NULL)
	{
		ret = MallocError;
		printf("malloc() err \n");
		return NULL;
	}

	ret = readn(tmpBuf, n); //根据长度读数据
	if (ret == -1)
	{
		printf("func readn() err:%d \n", ret);
        return QString();
	}
	else if (ret < n)
	{
		printf("func readn() err peer closed:%d \n", ret);
        return QString();
	}

	tmpBuf[n] = '\0'; //多分配一个字节内容，兼容可见字符串 字符串的真实长度仍然为n
    QByteArray data = QByteArray(tmpBuf, n);
	// 释放内存
	free(tmpBuf);

	return data;
}

void TcpSocket::disConnect()
{
#ifdef Q_OS_WIN
    closesocket(m_socket);
#endif
#ifdef Q_OS_LINUX
    if (m_socket >= 0)
    {
        close(m_socket);
    }
#endif
}

/////////////////////////////////////////////////
//////             子函数                   //////
/////////////////////////////////////////////////
/*
* readTimeout - 读超时检测函数，不含读操作
* @wait_seconds: 等待超时秒数，如果为0表示不检测超时
* 成功（未超时）返回0，失败返回-1，超时返回-1并且errno = ETIMEDOUT
*/
int TcpSocket::readTimeout(unsigned int wait_seconds)
{
	int ret = 0;
	if (wait_seconds > 0)
	{
		fd_set read_fdset;
		struct timeval timeout;

		FD_ZERO(&read_fdset);
		FD_SET(m_socket, &read_fdset);

		timeout.tv_sec = wait_seconds;
		timeout.tv_usec = 0;

#ifdef Q_OS_WIN
        int nfds = 0;
#endif
#ifdef Q_OS_LINUX
        int nfds = m_socket + 1;
#endif
		do
		{
            ret = select(nfds, &read_fdset, NULL, NULL, &timeout);
		} while (ret < 0 && errno == EINTR);

		if (ret == 0)
		{
			ret = -1;
			errno = ETIMEDOUT;
		}
		else if (ret == 1)
		{
			ret = 0;
		}
	}

	return ret;
}

/*
* writeTimeout - 写超时检测函数，不含写操作
* @wait_seconds: 等待超时秒数，如果为0表示不检测超时
* 成功（未超时）返回0，失败返回-1，超时返回-1并且errno = ETIMEDOUT
*/
int TcpSocket::writeTimeout(unsigned int wait_seconds)
{
	int ret = 0;
	if (wait_seconds > 0)
	{
		fd_set write_fdset;
		struct timeval timeout;

		FD_ZERO(&write_fdset);
		FD_SET(m_socket, &write_fdset);
		timeout.tv_sec = wait_seconds;
		timeout.tv_usec = 0;
#ifdef Q_OS_WIN
        int nfds = 0;
#endif
#ifdef Q_OS_LINUX
        int nfds = m_socket + 1;
#endif
		do
		{
            ret = select(nfds, NULL, &write_fdset, NULL, &timeout);
		} while (ret < 0 && errno == EINTR);

		// 超时
		if (ret == 0)
		{
			ret = -1;
			errno = ETIMEDOUT;
		}
		else if (ret == 1)
		{
			ret = 0;	// 没超时
		}
	}

    return ret;
}

int TcpSocket::connectTimeout(struct sockaddr_in &addr, unsigned int wait_seconds)
{
    // 设置为非阻塞
    u_long mode = 1;
    ioctlsocket(m_socket, FIONBIO, &mode);
    // 连接服务器
    ::connect(m_socket, (struct sockaddr*)&addr, sizeof(addr));

    int ret = 0;
    if (wait_seconds > 0)
    {
        fd_set write_fdset;
        struct timeval timeout;

        FD_ZERO(&write_fdset);
        FD_SET(m_socket, &write_fdset);
        timeout.tv_sec = wait_seconds;
        timeout.tv_usec = 0;
#ifdef Q_OS_WIN
        int nfds = 0;
#endif
#ifdef Q_OS_LINUX
        int nfds = m_socket + 1;
#endif
        do
        {
            ret = select(nfds, NULL, &write_fdset, NULL, &timeout);
        } while (ret < 0 && errno == EINTR);


        if (ret == 1)
        {
            int error, error_len;
            error_len = sizeof(error);
            getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char *)&error, &error_len);//获得错误号
            if(errno != 0)
            {
                ret = -1;
            }
            else
            {
                ret = 0;
            }
        }
        else
        {
            ret = -1;
        }
    }

    mode = 0;
    ioctlsocket(m_socket, FIONBIO, &mode);  // 设置为阻塞模式

    return ret;
}

/*
* readn - 读取固定字节数
* @fd: 文件描述符
* @buf: 接收缓冲区
* @count: 要读取的字节数
* 成功返回count，失败返回-1，读到EOF返回<count
*/
int TcpSocket::readn(void *buf, int count)
{
	size_t nleft = count;
	ssize_t nread;
	char *bufp = (char*)buf;

	while (nleft > 0)
	{
        if ((nread = recv(m_socket, bufp, nleft, 0)) < 0)
		{
			if (errno == EINTR)
			{
				continue;
			}
			return -1;
		}
		else if (nread == 0)
		{
			return count - nleft;
		}

		bufp += nread;
		nleft -= nread;
	}

	return count;
}

/*
* writen - 发送固定字节数
* @buf: 发送缓冲区
* @count: 要读取的字节数
* 成功返回count，失败返回-1
*/
int TcpSocket::writen(const void *buf, int count)
{
	size_t nleft = count;
	ssize_t nwritten;
	char *bufp = (char*)buf;

	while (nleft > 0)
	{
        if ((nwritten = send(m_socket, bufp, nleft, 0)) < 0)
		{
			if (errno == EINTR)	// 被信号打断
			{
				continue;
			}
			return -1;
		}
		else if (nwritten == 0)
		{
			continue;
		}

		bufp += nwritten;
		nleft -= nwritten;
	}

	return count;
}
